package com.bblocks.common;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.bblocks.pojo.ComUser;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.bblocks.common.auth.IgnoreLog;
import com.bblocks.enums.MDCFieldEnum;

import com.bblocks.util.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.data.domain.Pageable;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

/**
 * 日志拦截
 * 支持 @IgnoreLog ,控制是否忽略记录入参（比如入参太大或者文件上传）
 * @author Jin
 */
@Aspect
//@Component
@Slf4j
public class LoggerAdvice {

    public static final String REQ_TIME = "LOGGER_REQ_TIME";
	public static final String IGNEORE_LOG_FLAG = "LOGGER_IGNEORE_LOG_FLAG";

    /**
     * 在这里定义切面的点，Pointcut的表达式语法需要匹配到你调用的方法中
     */
    @Pointcut("within(com.*.*.controller..*)||within(com.*.controller..*)")
    public void declareJoinPointExpression() {
    }

    @Before("declareJoinPointExpression()")
    public void addBeforeLogger(JoinPoint joinPoint) {
        try {
            HttpServletRequest request = ServletUtils.getRequest();

            String url = request.getRequestURL().toString();
            String method = request.getMethod();
            String uri = request.getRequestURI();
            String userAgent = request.getHeader("User-Agent");
			String from = request.getHeader(SecurityConstants.FROM);//来源自gateway则为空，否则是系统的名称
            String token = request.getHeader(Constant.ACCESS_TOKEN_KEY);
            ComUser user = (ComUser) request.getAttribute(Constant.USER);

			String params = null;//JSONUtils.toJSONString(request.getParameterMap());
			int ignoreLogFalg = 3;
			if(log.isInfoEnabled()) {
				MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
				Method reflectMethod = joinPoint.getTarget()
						.getClass()
						.getMethod(methodSignature.getMethod().getName(),
								methodSignature.getMethod().getParameterTypes());
				IgnoreLog ignoreLog = reflectMethod.getAnnotation(IgnoreLog.class);//是否忽略：1=忽略入参（入参太大），2=全部（入和出都忽略）
				/**
				 * 是否忽略：0=正常，1=忽略入参（入参太大） 2=忽略结果（结果太大），3=全部（入和出都忽略），4=完全不处理
				 */
				ignoreLogFalg = ignoreLog != null ? ignoreLog.value() : 0;

				if (0 == ignoreLogFalg || 2 == ignoreLogFalg) {
					//2.最关键的一步:通过这获取到方法的所有参数名称的字符串数组
					String[] parameterNames = methodSignature.getParameterNames();
					Map paramMap = new HashMap();
					int i = 0;
					for (Object arg : joinPoint.getArgs()) {
						i++;
						//https://www.cnblogs.com/fswhq/p/10702857.html
						if (arg instanceof ServletRequest || arg instanceof ServletResponse || arg instanceof MultipartFile
								|| arg instanceof BindingResult) {
							continue;
						}else if(arg instanceof Pageable){//json序列化会失败
							paramMap.put(parameterNames[i - 1], ((Pageable)arg).toString());
							continue;
						}else
							paramMap.put(parameterNames[i - 1], arg);
					}
					//log.info("入参map：{}",paramMap);
					params = JSON.toJSONString(paramMap);
				}else if(4 == ignoreLogFalg) {
					request.setAttribute(IGNEORE_LOG_FLAG, ignoreLogFalg);
					return;
				}else {
					params = "忽略记录入参";
				}
			}

            //String seq = TraceIdUtils.getTraceId(false);
            //request.setAttribute(Constant.REQ_SEQUENCE_KEY, seq);
            request.setAttribute(REQ_TIME, System.nanoTime());
			request.setAttribute(IGNEORE_LOG_FLAG, ignoreLogFalg);
			MDC.put(MDCFieldEnum.URL.name(),uri);
			MDC.put(MDCFieldEnum.TOKEN.name(),token);
			MDC.put(MDCFieldEnum.USER_TYPE.name(),user != null ? user.getAppType().toString() : null);
			MDC.put(MDCFieldEnum.USER_ID.name(),user != null?user.getId().toString():"");

			if(log.isInfoEnabled())
				log.info("uri: {}, method: {}, params: {},UserAgent:{},from:{}, token:{},useracc:{},role:{}",
						uri, method, params,userAgent, StrUtil.isEmpty(from)?"其他":from, token,user!= null?user.getId():"" ,user!=null?user.getAppType():null);
        } catch (Exception e) {
            // 日志记录异常
			log.warn("记录入参日志异常：",e);
        }
    }

	/**
	 * 正常返回
	 * @param result
	 */
    @AfterReturning(returning="result",pointcut="declareJoinPointExpression()")
    public void exAfterReturning(Object result){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
		Integer ignoreLogFalg = (Integer) request.getAttribute(IGNEORE_LOG_FLAG);
		if(Integer.valueOf(4).equals(ignoreLogFalg) )
			return;
        String uri = request.getRequestURI();
        Long reqTime = (Long)request.getAttribute(REQ_TIME);
		long timeCost = -1L;
		if(reqTime != null)
			timeCost = System.nanoTime() - reqTime;
		MDC.put(MDCFieldEnum.COST.name(),String.valueOf(timeCost));
		if(log.isInfoEnabled()) {
			//是否忽略：0=正常，1=忽略入参（入参太大） 2=忽略结果（结果太大），3=全部（入和出都忽略）
			if (ignoreLogFalg == null || ignoreLogFalg < 2)
				log.info("响应:{}({}) 耗时:{} 响应：{}", uri,request.getHeader("Accept"), String.format("%.2f", (double) timeCost / 1000000), JSON.toJSONString(result));
			else
				log.info("响应:{}({}) 耗时:{} 响应：{}", uri,request.getHeader("Accept"), String.format("%.2f", (double) timeCost / 1000000), "忽略记录");
		}
    }

	/**
	 * 异常情况返回
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(value = "declareJoinPointExpression()",throwing = "e")
	public void afterThrowing(JoinPoint joinPoint, Exception e) {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();
		Integer ignoreLogFalg = (Integer) request.getAttribute(IGNEORE_LOG_FLAG);
		if(Integer.valueOf(4).equals(ignoreLogFalg) )
			return;
		String uri = request.getRequestURI();
		Long reqTime = (Long)request.getAttribute(REQ_TIME);
		long timeCost = -1L;
		if(reqTime != null)
			timeCost = System.nanoTime() - reqTime;
		MDC.put(MDCFieldEnum.COST.name(),String.valueOf(timeCost));
		if(log.isInfoEnabled()) {//异常都不忽略
			log.info("响应:{}({}) 耗时:{} 响应：{}", uri,request.getHeader("Accept"), String.format("%.2f", (double) timeCost / 1000000), e.toString());
		}
	}


    //返回值类型为Map<String, Object>
    public static Map<String, Object> getParameterMap(HttpServletRequest request) {
        Map<String, String[]> properties = request.getParameterMap();//把请求参数封装到Map<String, String[]>中
        Map<String, Object> returnMap = new HashMap<String, Object>();
        Iterator<Entry<String, String[]>> iter = properties.entrySet().iterator();
        String name = "";
        String value = "";
        while (iter.hasNext()) {
            Entry<String, String[]> entry = iter.next();
            name = entry.getKey();
            Object valueObj = entry.getValue();
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) { //用于请求参数中有多个相同名称
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length() - 1);
            } else {
                value = valueObj.toString();//用于请求参数中请求参数名唯一
            }
            returnMap.put(name, value);
        }
        return returnMap;
    }

}
